package featuresProcessing;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import lp.polimi.treefinder.utils.RankElement;
import utils.DBManager;
import utils.Species;

/**
 * An implementation of the rank aggregator. Some further details in following comments...
 */
public class KNNBasedRankGenerator implements RankGenerator {

	private static final int MAX_RANK_SIZE = 12;




	@Override
	public ArrayList<RankElement> computeRank(HashMap<Integer, Float> distancesByFeatures, HashMap<Integer, Float> distancesByShapes, ArrayList<Species> db) {
		ArrayList<RankElement> finalRanking = new ArrayList<RankElement>();
		
//		distancesByShapes = new HashMap<>();
		
		Set<Integer> allRankedLeaves = new HashSet<Integer>();
		allRankedLeaves.addAll(distancesByFeatures.keySet());
		allRankedLeaves.addAll(distancesByShapes.keySet());
		
		HashMap<Integer, Float> aggregatedDistances = new HashMap<Integer, Float>();
		Iterator<Integer> it0 = allRankedLeaves.iterator();
		while(it0.hasNext()) {
			
			Integer currLeaf = it0.next();
			
			Float df, ds;
			
			/* Compute aggregated distances by summing distances of leaves found in both the rankings (this is not generally true,
			 * as the Feature Detector ranking by design doesn't return all the leaves, but only the ones for which at least one
			 * matching key point is found, while the Shape Detector ranking is clipped). If a leaf is found in a ranking but not in
			 * the other one, the distance related to the missing leaf is set to 1. */
			if (distancesByFeatures.get(currLeaf)==null)
				df=1f;
			else
				df=distancesByFeatures.get(currLeaf);
			
			if (distancesByShapes.get(currLeaf)==null)
				ds=1f;
			else
				ds=distancesByShapes.get(currLeaf);
			
			aggregatedDistances.put(currLeaf, (float)(df+ds));
		}
		
		
		/* Leaves in ranking are identified by IDs. But it could be possible that different IDs belongs to the same species.
		 * Here a global ranking per species is computed, in order to increase the score of species that occurs several times in
		 * the rankings.
		 */
		Set<String> allRankedSpecies = new HashSet<String>();
		
		Iterator<Integer> it4 = aggregatedDistances.keySet().iterator();
		
		while (it4.hasNext()) {
			Integer tmp = it4.next();
			allRankedSpecies.add(db.get(tmp).getName());
		}
		
		HashMap<String, Float> finalSpeciesDistances = new HashMap<String, Float>();
		
		Iterator<String> it5 = allRankedSpecies.iterator();
		while (it5.hasNext()) {
			ArrayList<Float> speciesDists = new ArrayList<Float>();
			String species = it5.next();
			Iterator<Integer> intIt = aggregatedDistances.keySet().iterator();
			while (intIt.hasNext()) {
				Integer key = intIt.next();
				if (db.get(key).getName().equals(species)) {
					speciesDists.add(aggregatedDistances.get(key));
				}
			}
			Float num = 0f, den = 0f;
			for (int i = 0; i < speciesDists.size(); i++) {
				Float dist = speciesDists.get(i);
				num += 1/dist;
				den += 1/(dist*dist);
			}
			finalSpeciesDistances.put(species, num/den);
		}
		
		
		SortedSet<Float> orderedDistances = new TreeSet<Float>();
		orderedDistances.addAll(finalSpeciesDistances.values());
		Float maxDist = 1.1f*orderedDistances.iterator().next();
		Iterator<Float> it = orderedDistances.iterator();
		Set<String> keys = finalSpeciesDistances.keySet();
		while (it.hasNext()) {
			Float currentDistance = it.next();
			Iterator<String> it2 = keys.iterator();
			while (it2.hasNext() && finalRanking.size()<MAX_RANK_SIZE) {
				String name = it2.next();
				Float distance = finalSpeciesDistances.get(name);
				if (Math.abs(distance-currentDistance)<0.0000001 && distance<=maxDist)
					try {
//						System.out.println(name+" "+distance);
						finalRanking.add(getRankElement(name));
					} catch (SQLException e) {
						e.printStackTrace();
					}
			}
		}
		/* A final list of Ranking Elements is returned, where a Ranking Element is just a structure containing name and
		 * image of the species.
		 */
		return finalRanking;
	}




	private RankElement getRankElement(String name) throws SQLException {
		Statement stmt = DBManager.getConnection().createStatement();
		stmt.execute("select image from "+MatchingSystem.DB_SCHEMA_NAME+".images where name='"+name+"'");
		ResultSet rs = stmt.getResultSet();
		rs.next();
		RankElement element = new RankElement(rs.getBytes(1), name);
		DBManager.closeResources(stmt, rs);
		return element;
	}

}
